// ============================================================================
//
// Copyright (C) 2006-2008 Dengues
//
// Google Group: http://groups.google.com/group/dengues
// QQ Group: 24885404
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// ============================================================================
package org.dengues.commons.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.dengues.commons.IDenguesColorConstants;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;

/**
 * Qiang.Zhang.Adolf@gmail.com class global comment. Detailled comment <br/>
 * 
 * $Id: Dengues.epf 1 2006-09-29 17:06:40Z qiang.zhang $
 * 
 */
public class ColorUtil {

    /**
     * This map stores color name - Color pairs, used to quickly lookup a Color of a predefined color.
     * 
     * @param red red value of RGB
     * @param green green value of RGB
     * @param blue blue value of RGB
     */
    public static Color getColorII(int red, int green, int blue) {
        return getColorII(new RGB(red, green, blue));
    }

    /**
     * This map stores color name - Color pairs, used to quickly lookup a Color of a predefined color.
     * 
     * @param rgb RGB value of color
     */
    public static Color getColorII(RGB rgb) {
        if (rgb == null) {
            return null;
        }

        String key = rgb.toString();
        Color color = JFaceResources.getColorRegistry().get(key);
        if (color == null) {
            JFaceResources.getColorRegistry().put(key, rgb);
            color = JFaceResources.getColorRegistry().get(key);
        }
        return color;
    }

    /**
     * This map stores color name - Color pairs, used to quickly lookup a Color of a predefined color.
     * 
     * @param c color value
     */
    public static Color getColorII(int c) {
        RGB rgb = getRGBValue(c);
        return getColor(rgb);
    }

    /**
     * Converts an Integer value to an RGB object value, the Integer format is 0xRRGGBB.
     * 
     * @param rgbValue Integer value.
     * @return RGB value.
     */
    public static RGB getRGBValue(int rgbValue) {
        if (rgbValue == -1) {
            return null;
        }

        return new RGB((rgbValue >> 16) & 0xff, (rgbValue >> 8) & 0xff, rgbValue & 0xff);
    }

    /**
     * It is refined method in org.eclipse.dreaw2d.FigureUtilities which produces Color leak.
     */
    private static final float RGB_VALUE_MULTIPLIER = 0.6f;

    public static Color darker(Color color) {
        return getColor((int) (color.getRed() * RGB_VALUE_MULTIPLIER), (int) (color.getGreen() * RGB_VALUE_MULTIPLIER),
                (int) (color.getBlue() * RGB_VALUE_MULTIPLIER));
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "lighter".
     * 
     * @param color
     * @return
     */
    public static Color lighter(Color color) {
        int red = Math.min(255, (int) (color.getRed() * 1.2));
        int green = Math.min(255, (int) (color.getGreen() * 1.2));
        int blue = Math.min(255, (int) (color.getBlue() * 1.2));
        return getColor(red, green, blue);
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "lighter".
     * 
     * @param color
     * @param factor
     * @return
     */
    public static Color lighter(Color color, float factor) {
        int red = Math.min(255, (int) (color.getRed() * factor));
        int green = Math.min(255, (int) (color.getGreen() * factor));
        int blue = Math.min(255, (int) (color.getBlue() * factor));
        return getColor(red, green, blue);
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getColor".
     * 
     * @param red
     * @param green
     * @param blue
     * @return
     */
    public static Color getColor(int red, int green, int blue) {
        return getColor(new RGB(red, green, blue));
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getColor".
     * 
     * @param rgb
     * @return
     */
    public static Color getColor(String rgb) {
        String[] split = rgb.split(";");
        return getColor(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getColor".
     * 
     * @param rgb
     * @return
     */
    public static Color getColor(RGB rgb) {
        return new Color(null, rgb);
    }

    /**
     * Qiang.Zhang.Adolf@gmail.com Comment method "getAgainest".
     * 
     * @param rgb
     * @return
     */
    public static Color getAgainest(RGB rgb) {
        int red = 255 - rgb.red;
        int green = 255 - rgb.green;
        int blue = 255 - rgb.blue;
        return getColor(red, green, blue);
    }

    /**
     * Stores rgb value keyed by css color name.
     */

    static Map cssToRgbMap = new HashMap();

    /**
     * Stores css color name keyed by rgb value.
     */

    static Map rgbToCssMap = new HashMap();

    /**
     * Static table stores the colors. It provides the name and the rgb value of the colors. This should match the color
     * list in ColorPropertyType.
     */

    static final String[][] colors = { { IDenguesColorConstants.MAROON, "#800000" }, //$NON-NLS-1$  
            { IDenguesColorConstants.RED, "#FF0000" }, //$NON-NLS-1$  
            { IDenguesColorConstants.ORANGE, "#ffA500" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.YELLOW, "#FFFF00" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.OLIVE, "#808000" }, //$NON-NLS-1$  
            { IDenguesColorConstants.PURPLE, "#800080" }, //$NON-NLS-1$  
            { IDenguesColorConstants.FUCHSIA, "#FF00FF" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.WHITE, "#FFFFFF" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.LIME, "#00FF00" }, //$NON-NLS-1$  
            { IDenguesColorConstants.GREEN, "#008000" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.NAVY, "#000080" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.BLUE, "#0000FF" }, //$NON-NLS-1$ 
            { IDenguesColorConstants.AQUA, "#00FFFF" }, //$NON-NLS-1$  
            { IDenguesColorConstants.TEAL, "#008080" }, //$NON-NLS-1$  
            { IDenguesColorConstants.BLACK, "#000000" }, //$NON-NLS-1$  
            { IDenguesColorConstants.SILVER, "#C0C0C0" }, //$NON-NLS-1$  
            { IDenguesColorConstants.GRAY, "#808080" } //$NON-NLS-1$              
    };

    static {
        // initialize the rgb map, pair colors with their rgb values.

        for (int i = 0; i < colors.length; i++) {
            Integer rgb = Integer.decode(colors[i][1]);

            cssToRgbMap.put(colors[i][0], rgb);
            rgbToCssMap.put(rgb, colors[i][0]);
        }
    }

    /**
     * Regular expression for "RGB( 255, 0, 0 )", integer range 0 - 255
     */

    private static final String COLOR_CSS_PATTERN = "[rR][gG][bB][(]" + //$NON-NLS-1$
            "[\\s]*[\\d]+[\\s]*[,]" + //$NON-NLS-1$
            "[\\s]*[\\d]+[\\s]*[,]" + //$NON-NLS-1$
            "[\\s]*[\\d]+[\\s]*" + //$NON-NLS-1$
            "[)]"; //$NON-NLS-1$

    /**
     * Regular expression for "RGB( 255.0%, 0.0%, 0.0% )", float range 0.0% - 100.0%
     */

    private static final String COLOR_CSS_PERCENT_PATTERN = "[rR][gG][bB][(]" + //$NON-NLS-1$
            "[\\s]*[\\d]+(.\\d+)?[\\s]*[%][\\s]*[,]" + //$NON-NLS-1$
            "[\\s]*[\\d]+(.\\d+)?[\\s]*[%][\\s]*[,]" + //$NON-NLS-1$
            "[\\s]*[\\d]+(.\\d+)?[\\s]*[%][\\s]*" + //$NON-NLS-1$
            "[)]"; //$NON-NLS-1$

    /**
     * Compiled pattern for CSS absolute pattern: "RGB( 255, 0, 0 )"
     */

    private static Pattern cssAbsolutePattern = Pattern.compile(COLOR_CSS_PATTERN);

    /**
     * Compiled pattern for CSS relative pattern: "RGB( 255%, 0%, 0% )"
     */
    private static Pattern cssRelativePattern = Pattern.compile(COLOR_CSS_PERCENT_PATTERN);

    /**
     * Color display preference for HTML style: #RRGGBB.
     * 
     * #RRGGBB
     */

    public final static int HTML_FORMAT = 1;

    /**
     * Useful constant for Color display preference, display Color as integer.
     */

    public final static int INT_FORMAT = 0;

    /**
     * Color display preference for JAVA style: 0xRRGGBB.
     */

    public final static int JAVA_FORMAT = 2;

    /**
     * Color display preference for CSS absolute style: RGB(r,g,b).
     */

    public final static int CSS_ABSOLUTE_FORMAT = 3;

    /**
     * Color display preference for CSS relative style: RGB(r%,g%,b%).
     */

    public final static int CSS_RELATIVE_FORMAT = 4;

    /**
     * Default format for display preference: <code>CSS_ABSOLUTE_FORMAT</code>.
     */

    public final static int DEFAULT_FORMAT = CSS_ABSOLUTE_FORMAT;

    /**
     * Gets the integer value of a predefined color. The <code>color</code> should be a predefined color name,
     * otherwise <code>-1</code> is returned.
     * 
     * @param color a given color name, it is case insensitive.
     * @return the integer value of a predefined color, return -1 if the name of the given color is not defined.
     * 
     */

    public static int parsePredefinedColor(String color) {
        if (color == null)
            return -1;

        Integer rgbValue = (Integer) cssToRgbMap.get(color.toLowerCase());
        return rgbValue == null ? -1 : rgbValue.intValue();
    }

    /**
     * Gets a css predefined color given its rgb value.
     * 
     * @param rgb integer rgb value.
     * @return a css predefined color if there is a predefined color matches the given <code>rgb</code>, return
     * <code>null</code> otherwise.
     */

    public static String getPredefinedColor(int rgb) {
        return (String) rgbToCssMap.get(new Integer(rgb));
    }

    /**
     * Indicates whether the color value is of valid css absolute format: "RGB(r,g,b)". The RGB prefix is case
     * insensitive and r, g, b should be integer value. Whitespace characters are allowed around the numerical values.
     * The followings are some cases of valid css absolute colors:
     * <p>
     * <ul>
     * <li>RGB(255,0,0)</li>
     * <li>Rgb( 255, 0, 0)</li>
     * <li>rgb(300,300,300)</li>
     * </ul>
     * 
     * @param value a string color value
     * @return <code>true</code> if the color value is in a valid css absolute color representation.
     */

    public static boolean isCssAbsolute(String value) {
        return cssAbsolutePattern.matcher(value).matches();
    }

    /**
     * Indicates whether the color value is of a valid css relative format: "RGB( r%, g%, b%)". The RGB prefix is case
     * insensitive and r, g, b should be a float value. Whitespace characters are allowed around the numerical values.
     * The followings are some cases of valid css relative colors:
     * 
     * <p>
     * <ul>
     * <li>RGB(100%,0%,0%)</li>
     * <li>Rgb( 100% , 0% , 0% )</li>
     * <li>rgb(200%,200%,200%)</li>
     * </ul>
     * 
     * @param value a string color value
     * @return <code>true</code> if the color value is in a valid css relative. color representation.
     */

    public static boolean isCssRelative(String value) {
        return cssRelativePattern.matcher(value).matches();
    }

    /**
     * Formats an integer RGB value according to the format preference provided. An integer RGB value can be formatted
     * as follows:
     * <ul>
     * <li>INT_FORMAT, display color as an integer.</li>
     * <li>HTML_FORMAT ( #RRGGBB )</li>
     * <li>JAVA_FORMAT ( 0xRRGGBB )</li>
     * <li>CSS_ABSOLUTE_FORMAT ( RGB(r,g,b) )</li>
     * <li>CSS_RELATIVE_FORMAT ( RGB(r%,g%,b%) )</li>
     * </ul>
     * <p>
     * The integer value will first be converted into 6-digits hex format(filling "0" from left), so only the right most
     * 6 digits will be used.
     * 
     * @param rgbValue integer RGB value for a color
     * @param rgbFormat Color display preference, one of Color display preference constants. For example,
     * CSS_ABSOLUTE_FORMAT that will convert into style "RGB(255,0,0)". If the preference provided is not in the
     * predefined list, then {@link ColorUtil#CSS_ABSOLUTE_FORMAT}will be applied.
     * 
     * @return a string representation of the color in the target format.
     * 
     * @see ColorUtil#INT_FORMAT
     * @see ColorUtil#HTML_FORMAT
     * @see ColorUtil#JAVA_FORMAT
     * @see ColorUtil#CSS_ABSOLUTE_FORMAT
     * @see ColorUtil#CSS_RELATIVE_FORMAT
     * @see ColorUtil#DEFAULT_FORMAT
     * 
     */

    public static String format(int rgbValue, int rgbFormat) {
        // to #FF0000

        String rgbText = StringUtils.toRgbText(rgbValue).toUpperCase();
        switch (rgbFormat) {
        case ColorUtil.INT_FORMAT:
            return String.valueOf(rgbValue);
        case ColorUtil.HTML_FORMAT:
            return rgbText;
        case ColorUtil.JAVA_FORMAT:
            return rgbText.replaceFirst("#", "0x"); //$NON-NLS-1$ //$NON-NLS-2$
        case ColorUtil.CSS_ABSOLUTE_FORMAT:
            return hexToRGB(rgbText, true);
        case ColorUtil.CSS_RELATIVE_FORMAT:
            return hexToRGB(rgbText, false);
        default:
            // use default
            return hexToRGB(rgbText, true);
        }
    }

    /**
     * Formats an color value according to the format preference provided. See {@link #parseColor(String)}for the
     * allowed color value representations.
     * 
     * @param value a given string containing one of the allowed notation.
     * @param rgbFormat Color display preference, one of Color display preference constants. For example,
     * CSS_ABSOLUTE_FORMAT that will convert into style "RGB(255,0,0)". If the preference provided is not in the
     * predefined list, then {@link ColorUtil#CSS_ABSOLUTE_FORMAT}will be applied.
     * @return a string representation of the color in the target format.
     * @exception NumberFormatException if the <code>String</code> representing a numerical value does not contain a
     * parsable integer.
     * 
     * @see ColorUtil#INT_FORMAT
     * @see ColorUtil#HTML_FORMAT
     * @see ColorUtil#JAVA_FORMAT
     * @see ColorUtil#CSS_ABSOLUTE_FORMAT
     * @see ColorUtil#CSS_RELATIVE_FORMAT
     * @see ColorUtil#DEFAULT_FORMAT
     * 
     * @see #parseColor(String)
     * @see #format(int, int)
     * 
     */

    public static String format(String value, int rgbFormat) {
        int rgbValue = parseColor(value);
        if (rgbValue != -1)
            return format(rgbValue, rgbFormat);

        return null;
    }

    /**
     * Convert an hex color text into an css absolute or relative format. For example, "#FF0000" to "RGB(255, 0, 0) or
     * to "RGB( 100.0%, 0%, 0%)s".
     * 
     * @param hexColor an hex color text
     * @param isAbsolute if <tt>true</tt>, return will be in css absolute format, e.g, RGB(255,0,0); if
     * <tt>false</tt>, return will be in css relative format, e.g, RGB(100.0%,0%,0%).
     * 
     * @return a formatted css relative or absolute color format.
     */

    private static String hexToRGB(String hexColor, boolean isAbsolute) {
        assert hexColor.length() == 7;

        // convert from "#FF0000" to "RGB(255, 0, 0)" or to "RGB( 100.0%, 0%, 0%
        // )"

        String r = hexColor.substring(1, 3);
        String g = hexColor.substring(3, 5);
        String b = hexColor.substring(5, 7);

        r = Integer.valueOf(r, 16).toString();
        g = Integer.valueOf(g, 16).toString();
        b = Integer.valueOf(b, 16).toString();

        StringBuffer sb = new StringBuffer();

        // RGB(r,g,b) or RGB(r%,g%,b%)

        if (isAbsolute) {
            sb.append("RGB(").append(r).append(","); //$NON-NLS-1$ //$NON-NLS-2$
            sb.append(g).append(","); //$NON-NLS-1$
            sb.append(b).append(")"); //$NON-NLS-1$
        } else {
            int r_iValue = Integer.valueOf(r).intValue();
            int g_iValue = Integer.valueOf(g).intValue();
            int b_iValue = Integer.valueOf(b).intValue();

            // round to 1 digit after the comma.

            float r_fValue = ((int) (r_iValue * 10 * 100 / 255f + 0.5f)) / 10.0f;
            float g_fValue = ((int) (g_iValue * 10 * 100 / 255f + 0.5f)) / 10.0f;
            float b_fValue = ((int) (b_iValue * 10 * 100 / 255f + 0.5f)) / 10.0f;

            sb.append("RGB(").append(String.valueOf(r_fValue)).append("%,"); //$NON-NLS-1$ //$NON-NLS-2$
            sb.append(String.valueOf(g_fValue)).append("%,"); //$NON-NLS-1$
            sb.append(String.valueOf(b_fValue)).append("%)"); //$NON-NLS-1$
        }

        return sb.toString();
    }

    /**
     * Parse a valid css color expressed in absolute or relative format as "RGB(r%,g%,b%)" or "RGB(r,g,b)" and return
     * the integer value of the color.
     * 
     * @param rgbColor input color value of a valid css relative or absolute format.
     * @return an integer value of the color representation. Return <code>-1</code> if the string is not in a valid
     * absolute or relative representation.
     * 
     * @see #isCssAbsolute(String)
     * @see #isCssRelative(String)
     */

    private static int parseRGBColor(String rgbColor) {
        // not valid, return -1.

        if (!isCssAbsolute(rgbColor) && !isCssRelative(rgbColor))
            return -1;

        boolean hasPercentage = false;

        int start = rgbColor.indexOf('(');
        int end = rgbColor.indexOf(')');

        // cut from "rgb(255,0,0)" to "255, 0, 0", rgb(100%,100%,100%) to
        // "100%,100%,100%"

        String subStr1 = rgbColor.substring(start + 1, end).trim();

        if (subStr1.indexOf('%') != -1) {
            // get rid of '%'
            subStr1 = subStr1.replace('%', ' ');
            hasPercentage = true;
        }

        // split into 3 strings, we got {255,0,0}

        String[] numbers = subStr1.split(","); //$NON-NLS-1$

        // #FF0000
        String colorValue = "#"; //$NON-NLS-1$

        // parse String( base 10 ) into String( base 16 )

        for (int i = 0; i < 3; i++) {
            int intValue = 0;

            String number = numbers[i].trim();

            if (hasPercentage) {
                float value = Float.parseFloat(number);

                if (value > 100.0f)
                    value = 100.0f;

                // 100.0% => 255, 0.0% => 0

                intValue = (int) (value * 255.0f / 100.0f + 0.5f);
            } else {
                intValue = Integer.parseInt(number);

                // e,g. clip "300" to "255"
                if (intValue > 255)
                    intValue = 255;
            }

            // Fill "f" to "0f"

            String strValue = "0" + Integer.toHexString(intValue); //$NON-NLS-1$
            strValue = strValue.substring(strValue.length() - 2);

            colorValue = colorValue + strValue;
        }

        return Integer.decode(colorValue).intValue();

    }

    /**
     * Parses the string color value as a color keyword or a numerical RGB notation, return its corresponding rgb
     * integer value. The string value can be one of the followings:
     * 
     * <ul>
     * <li>A decimal number: "16711680". The number will be clipped into 0~#FFFFFF</li>
     * <li>A hexadecimal number in HTML format. '#' immediately followed by either three or six hexadecimal characters.
     * The three-digit RGB notation (#rgb) is converted into six-digit form (#rrggbb) by replicating digits, not by
     * adding zeros. For example, #fb0 expands to #ffbb00. Values outside "#FFFFFF" will be clipped.</li>
     * <li>A hexadecimal number in Java format: "0xRRGGBB"</li>
     * <li>A css predefined color name: "red", "green", "yellow" etc.</li>
     * <li>A css absolute or relative notation: "rgb(r,g,b)" or "rgb(r%,g%,b%)". 'rgb(' followed by a comma-separated
     * list of three numerical values (either three integer values in the range of 0-255, or three percentage values in
     * the range of 0.0% to 100.0%) followed by '), Values outside the numerical ranges will be clipped. Whitespace
     * characters are allowed around the numerical values.</li>
     * </ul>
     * <p>
     * These examples given a allowed color value that can be parsed into an integer.
     * <ul>
     * <li>"16711680"</li>
     * <li>"#FFFF00"</li>
     * <li>"#FFFF00F", will be clipped into "#FFFFFF"</li>
     * <li>"0xFF00FF"</li>
     * <li>"red" or "green"</li>
     * <li>rgb(255,0,0)</li>
     * <li>rgb(100.0%,0%,0%)</li>
     * </ul>
     * 
     * @param value a given string containing one of the allowed notation.
     * @return the integer value of the color, return <code>-1</code> if the value is not in one of the allowed
     * format. If the value is in a valid integer format, return value will be clipped to 0 ~ 0xFFFFFF
     * 
     * 
     */

    public static int parseColor(String value) {
        if (StringUtils.isBlank(value))
            return -1;

        // 1. Is the value a hexadecimal number or decimal number. It can be
        // six-digit form (#rrggbb) or three-digit form (#rgb) or java style
        // as (0xRRGGBB )

        int first = value.charAt(0);
        if (first == '#' || (first >= '0' && first <= '9')) {
            if (first == '#' && value.length() == 4) {
                // #RGB
                char[] rgb_chars = value.toCharArray();
                char[] rrggbb_chars = { '#', rgb_chars[1], rgb_chars[1], rgb_chars[2], rgb_chars[2], rgb_chars[3], rgb_chars[3] };

                value = String.valueOf(rrggbb_chars);
            }

            try {
                int retValue = Integer.decode(value).intValue();

                if (retValue > 0xFFFFFF)
                    return 0xFFFFFF;

                return retValue;
            } catch (NumberFormatException e) {
                return -1;
            }

        }

        // 2. Is this a predefined color?

        int rgbValue = parsePredefinedColor(value);
        if (rgbValue != -1)
            return rgbValue;

        // 3. CSS absolute or relative format: {rgb(r,g,b)} or {rgb(r%,g%,b%)}

        if (isCssAbsolute(value) || isCssRelative(value))
            return parseRGBColor(value);

        return -1;
    }

    /**
     * Returns the Red, Blue, Green value for a integer RGB color value. The given RGB value should be in the scope of
     * (0~0xFFFFFF), otherwise return <code>null</code>.
     * 
     * @param rgbValue a given integer RGB color value.
     * @return an array containing Red, Blue, Green separately. Return <code>null</code> if the value is not in
     * (0~0xFFFFFF).
     */

    public static int[] getRGBs(int rgbValue) {
        if (rgbValue > 0xFFFFFF || rgbValue < 0)
            return null;

        int rgb = rgbValue;

        int r = rgb >> 16;
        int g = (rgb >> 8) & 0xFF;
        int b = rgb & 0xFF;

        return new int[] { r, g, b };
    }

    /**
     * Returns the Red, Blue, Green value for a color value. The given string containing one of the allowed notations.
     * 
     * @param colorValue a given string color value in one of the allowed notations.
     * @return an array containing Red, Blue, Green separately. Return <code>null</code> if the given color value is
     * not parsable.
     */

    public static int[] getRGBs(String colorValue) {
        int rgb = parseColor(colorValue);
        return rgb == -1 ? null : getRGBs(rgb);
    }

    /**
     * Calculates the integer color value given its red, blue, green values. If any color factor is over 0xFF, it will
     * be clipped to 0xFF; if any color factor is below 0, it will be increased to 0.
     * 
     * 
     * @param r red value.
     * @param g green value.
     * @param b blue value.
     * @return the integer color value of the given color factors.
     */

    public static int formRGB(int r, int g, int b) {
        // clip to 0 ~ 0xFF

        r = r > 0xFF ? 0xFF : r < 0 ? 0 : r;
        g = g > 0xFF ? 0xFF : g < 0 ? 0 : g;
        b = b > 0xFF ? 0xFF : b < 0 ? 0 : b;

        return (r << 16) + (g << 8) + b;
    }
}
